تعلم كيفية تطبيق حدود الأخطاء (Error Boundaries) في React مع الـ Hooks للتعامل السلس مع أخطاء تحميل الموارد، مما يحسن تجربة المستخدم واستقرار التطبيق.
تحميل الموارد القوي في React: إتقان حدود الأخطاء (Error Boundaries) مع الـ Hooks
في تطبيقات الويب الحديثة، يعد تحميل الموارد بشكل غير متزامن ممارسة شائعة. سواء كان ذلك جلب بيانات من واجهة برمجة تطبيقات (API)، أو تحميل صور، أو استيراد وحدات، فإن التعامل مع الأخطاء المحتملة أثناء تحميل الموارد أمر بالغ الأهمية لتجربة مستخدم سلسة. توفر حدود الأخطاء (Error Boundaries) في React آلية لاكتشاف أخطاء JavaScript في أي مكان في شجرة المكونات الفرعية الخاصة بها، وتسجيل تلك الأخطاء، وعرض واجهة مستخدم احتياطية بدلاً من انهيار التطبيق بأكمله. يستكشف هذا المقال كيفية استخدام حدود الأخطاء بفعالية بالاقتران مع React Hooks لإدارة أخطاء تحميل الموارد.
فهم حدود الأخطاء (Error Boundaries)
قبل React 16، كانت أخطاء JavaScript غير المعالجة أثناء عرض المكونات تؤدي إلى إتلاف حالة React الداخلية وتسبب أخطاء غامضة في عمليات العرض اللاحقة. تعالج حدود الأخطاء هذه المشكلة من خلال العمل ككتل شاملة للأخطاء التي تحدث في مكوناتها الفرعية. إنها مكونات React تطبق إحدى طريقتي دورة الحياة التاليتين أو كلتيهما:
static getDerivedStateFromError(error): يتم استدعاء هذه الطريقة الثابتة بعد أن يتم طرح خطأ بواسطة مكون فرعي. تتلقى الخطأ الذي تم طرحه كوسيطة وتعيد قيمة لتحديث حالة المكون.componentDidCatch(error, info): يتم استدعاء طريقة دورة الحياة هذه بعد أن يتم طرح خطأ بواسطة مكون فرعي. تتلقى الخطأ الذي تم طرحه كوسيطة، بالإضافة إلى كائن يحتوي على معلومات حول المكون الذي طرح الخطأ. يمكنك استخدامها لتسجيل معلومات الخطأ.
من المهم ملاحظة أن حدود الأخطاء تلتقط الأخطاء فقط في مرحلة العرض (rendering)، وفي طرق دورة الحياة، وفي مُنشِئات (constructors) الشجرة بأكملها تحتها. هي لا تلتقط الأخطاء في:
- معالجات الأحداث (event handlers) (اعرف المزيد في القسم أدناه)
- الكود غير المتزامن (e.g.,
setTimeoutorrequestAnimationFramecallbacks) - العرض من جانب الخادم (Server-side rendering)
- الأخطاء التي يتم طرحها في حد الخطأ نفسه (بدلاً من مكوناته الفرعية)
حدود الأخطاء و React Hooks: مزيج قوي
بينما كانت المكونات الصنفية (class components) تستخدم تقليديًا لتطبيق حدود الأخطاء، تقدم React Hooks نهجًا أكثر إيجازًا ووظيفية. يمكننا إنشاء Hook قابل لإعادة الاستخدام useErrorBoundary يغلف منطق معالجة الأخطاء ويوفر طريقة ملائمة لتغليف المكونات التي قد تطرح أخطاء أثناء تحميل الموارد.
إنشاء Hook مخصص useErrorBoundary
إليك مثال على Hook useErrorBoundary:
import { useState, useCallback } from 'react';
function useErrorBoundary() {
const [error, setError] = useState(null);
const resetError = useCallback(() => {
setError(null);
}, []);
const captureError = useCallback((e) => {
setError(e);
}, []);
const ErrorBoundary = useCallback(({ children, fallback }) => {
if (error) {
return fallback ? fallback : An error occurred: {error.message || String(error)};
}
return children;
}, [error]);
return { ErrorBoundary, captureError, error, resetError };
}
export default useErrorBoundary;
شرح:
useState: نستخدمuseStateلإدارة حالة الخطأ. في البداية، يتم تعيين الخطأ إلىnull.useCallback: نستخدمuseCallbackلتخزين (memoize) الدالتينresetErrorوcaptureError. هذه ممارسة جيدة لمنع عمليات إعادة العرض غير الضرورية إذا تم تمرير هذه الدوال كخصائص (props).- مكون
ErrorBoundary: هذا مكون وظيفي تم إنشاؤه باستخدامuseCallbackيأخذchildrenوخاصيةfallbackاختيارية. إذا كان هناك خطأ في الحالة، فإنه يعرض إما مكونfallbackالمقدم أو رسالة خطأ افتراضية. وإلا، فإنه يعرض المكونات الأبناء (children). هذا يعمل بمثابة حد الخطأ الخاص بنا. تضمن مصفوفة الاعتمادية `[error]` إعادة عرضه عند تغير حالة `error`. - دالة
captureError: تُستخدم هذه الدالة لتعيين حالة الخطأ. ستقوم باستدعائها داخل كتلةtry...catchعند تحميل الموارد. - دالة
resetError: تقوم هذه الدالة بمسح حالة الخطأ، مما يسمح للمكون بإعادة عرض مكوناته الأبناء (مما قد يؤدي إلى إعادة محاولة تحميل المورد).
تطبيق تحميل الموارد مع معالجة الأخطاء
الآن، دعنا نرى كيفية استخدام هذا الـ Hook للتعامل مع أخطاء تحميل الموارد. لنأخذ في الاعتبار مكونًا يجلب بيانات المستخدم من واجهة برمجة تطبيقات (API):
import React, { useState, useEffect } from 'react';
import useErrorBoundary from './useErrorBoundary';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const { ErrorBoundary, captureError, error, resetError } = useErrorBoundary();
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (e) {
captureError(e);
}
};
fetchData();
}, [userId, captureError]);
if (error) {
return (
Failed to load user data. {user.name}
Email: {user.email}
{/* Other user details */}شرح:
- نقوم باستيراد الـ Hook
useErrorBoundary. - نستدعي الـ Hook للحصول على مكون
ErrorBoundary، ودالةcaptureError، وحالةerror، ودالةresetError. - داخل الـ Hook
useEffect، نقوم بتغليف استدعاء API في كتلةtry...catch. - إذا حدث خطأ أثناء استدعاء API، فإننا نستدعي
captureError(e)لتعيين حالة الخطأ. - إذا تم تعيين حالة
error، فإننا نعرض مكونErrorBoundary. نحن نوفر خاصيةfallbackمخصصة تعرض رسالة خطأ وزر "إعادة المحاولة". يؤدي النقر فوق الزر إلى استدعاءresetErrorلمسح حالة الخطأ، مما يؤدي إلى إعادة العرض ومحاولة أخرى لجلب البيانات. - إذا لم يحدث خطأ وتم تحميل بيانات المستخدم، فإننا نعرض تفاصيل ملف تعريف المستخدم.
التعامل مع أنواع مختلفة من أخطاء تحميل الموارد
قد تتطلب الأنواع المختلفة من أخطاء تحميل الموارد استراتيجيات معالجة مختلفة. إليك بعض السيناريوهات الشائعة وكيفية التعامل معها:
أخطاء الشبكة
تحدث أخطاء الشبكة عندما يكون العميل غير قادر على الاتصال بالخادم (على سبيل المثال، بسبب انقطاع الشبكة أو توقف الخادم). المثال أعلاه يعالج بالفعل أخطاء الشبكة الأساسية باستخدام `response.ok`. قد ترغب في إضافة كشف أخطاء أكثر تطوراً، على سبيل المثال:
//Inside the fetchData function
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
// Consider adding specific error code handling
if (response.status === 404) {
throw new Error("User not found");
} else if (response.status >= 500) {
throw new Error("Server error. Please try again later.");
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
const data = await response.json();
setUser(data);
} catch (error) {
if (error.message === 'Failed to fetch') {
// Likely a network error
captureError(new Error('Network error. Please check your internet connection.'));
} else {
captureError(error);
}
}
في هذه الحالة، يمكنك عرض رسالة للمستخدم تشير إلى وجود مشكلة في اتصال الشبكة وتقترح عليه التحقق من اتصاله بالإنترنت.
أخطاء API
تحدث أخطاء API عندما يعيد الخادم استجابة خطأ (على سبيل المثال، 400 Bad Request أو 500 Internal Server Error). كما هو موضح أعلاه، يمكنك التحقق من `response.status` والتعامل مع هذه الأخطاء بشكل مناسب.
أخطاء تحليل البيانات
تحدث أخطاء تحليل البيانات عندما لا تكون الاستجابة من الخادم بالتنسيق المتوقع ولا يمكن تحليلها (على سبيل المثال، JSON غير صالح). يمكنك التعامل مع هذه الأخطاء عن طريق تغليف استدعاء response.json() في كتلة try...catch:
//Inside the fetchData function
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (error) {
if (error instanceof SyntaxError) {
captureError(new Error('Failed to parse data from server.'));
} else {
captureError(error);
}
}
أخطاء تحميل الصور
لتحميل الصور، يمكنك استخدام معالج الحدث onError على وسم <img>:
function MyImage({ src, alt }) {
const { ErrorBoundary, captureError } = useErrorBoundary();
const [imageLoaded, setImageLoaded] = useState(false);
const handleImageLoad = () => {
setImageLoaded(true);
};
const handleImageError = (e) => {
captureError(new Error(`Failed to load image: ${src}`));
};
return (
Failed to load image.